﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using EuroManager.Domain.Simulator.AI;
using EuroManager.Domain.Simulator.Engine;
using EuroManager.Domain.Simulator.Helpers;
using EuroManager.Domain.Simulator.Tactics;

namespace EuroManager.Domain.Simulator.World
{
    public class Player : MovingEntity, IPlayer
    {
        public static readonly float MinPassReceivingSpeed = 8;
        public static readonly float MaxPassReceivingSpeed = 12;

        private static readonly float reachBallDistance = 1;
        private static readonly float acceleration = 4;
        private static readonly float deceleration = 6;
        private static readonly float maxSpeed = 7;
        private static readonly float ballPushForce = 5;
        private static readonly float shootingForce = 40;

        private int number;
        private ITeam team;
        private ITeam opponent;
        private IPlayerTactics tactics;
        private IPitch pitch;
        private IBall ball;
        private IRandomizer random;

        private TimeLocation reachBallLocation = null;

        public Player(int number, ITeam team, ITeam opponent, IPlayerTactics tactics, IPitch pitch, IBall ball, IRandomizer random, Vector2D location)
            : base(location, acceleration, deceleration, maxSpeed)
        {
            Contract.Requires(number > 0);
            Contract.Requires(team != null);
            Contract.Requires(opponent != null && opponent != team);
            Contract.Requires(tactics != null);
            Contract.Requires(pitch != null);
            Contract.Requires(ball != null);
            Contract.Requires(random != null);

            this.number = number;
            this.team = team;
            this.opponent = opponent;
            this.tactics = tactics;
            this.pitch = pitch;
            this.ball = ball;
            this.random = random;

            CalculateTacticalLocation();
            CalculateReachBallLocation();
            FindVisiblePlayers();
        }

        public IPlayerAI AI { get; set; }

        public int Number
        {
            get { return number; }
        }

        public ITeam Team
        {
            get { return team; }
        }

        public ITeam Opponent
        {
            get { return opponent; }
        }

        public PitchSide Side
        {
            get { return team.Side; }
        }

        public float TimeToReachBall
        {
            get { return reachBallLocation == null ? Single.PositiveInfinity : reachBallLocation.Time; }
        }

        public bool CanReachBall
        {
            get { return DistanceTo(ball.Location) <= reachBallDistance; }
        }

        public bool HasBall
        {
            get { return ball.Owner == this; }
        }

        public Vector2D TacticalLocation { get; private set; }

        public float DistanceToOpponentGoal
        {
            get { return DistanceTo(OpponentGoalLocation); }
        }

        public bool IsFacingOpponentGoal
        {
            get { return Vector2D.Dot(Heading, OpponentGoalDirection) > 0; }
        }

        private Vector2D OpponentGoalLocation
        {
            get { return pitch.GoalLocation(opponent.Side); }
        }

        private Vector2D OpponentGoalDirection
        {
            get { return DirectionTo(OpponentGoalLocation); }
        }

        public IEnumerable<IPlayer> VisiblePlayersFromOwnTeam { get; private set; }

        public override void Update(float elapsedTime)
        {
            base.Update(elapsedTime);

            CalculateTacticalLocation();
            CalculateReachBallLocation();
            FindVisiblePlayers();
        }

        public void Observe()
        {
            AI.Observe();
        }

        public void ChaseBall()
        {
            AI.ChaseBall();
        }

        public void ControlBall()
        {
            AI.ControlBall();
        }

        public void Run(Vector2D destination, float tolerance)
        {
            Arrive(destination, tolerance);
        }

        public void TurnToBall()
        {
            Turn(ball.Location);
        }

        public void RunToBall()
        {
            if (reachBallLocation != null)
            {
                Meet(reachBallLocation.Location, reachBallDistance, reachBallLocation.Time);
            }
        }

        public void TrapBall()
        {
            if (CanReachBall)
            {
                ball.Trap(this);
            }
        }

        public void PushBallForward()
        {
            ball.Kick(ballPushForce * OpponentGoalDirection);
        }

        public void PassBall(IPlayer player)
        {
            if (ball.Location != player.Location)
            {
                float receivePassSpeed = random.PlayerReceivePassSpeed(MinPassReceivingSpeed, MaxPassReceivingSpeed);
                ball.KickAt(player.Location, receivePassSpeed);
                ball.Free();
            }
        }

        public void Shoot()
        {
            ball.Kick(shootingForce * OpponentGoalDirection);
            ball.Free();
        }

        private void CalculateTacticalLocation()
        {
            TacticalLocation = tactics.CalculateTacticalLocation(pitch, Side, ball.Location);
        }

        private void CalculateReachBallLocation()
        {
            float time = 0;
            Vector2D location = Vector2D.Zero;

            foreach (TimeLocation futureLocation in ball.FutureLocations)
            {
                location = futureLocation.Location;
                time = TimeToReach(location);
                
                if (time <= futureLocation.Time)
                {
                    reachBallLocation = futureLocation;
                    return;
                }
            }

            reachBallLocation = new TimeLocation(time, location);
        }

        private float TimeToReach(Vector2D location)
        {
            float distance = (location - Location).Length;
            return Physics.Time(maxSpeed, distance);
        }

        private void FindVisiblePlayers()
        {
            VisiblePlayersFromOwnTeam = Team.Players.Where(p => p != this && Vector2D.Dot(Heading, DirectionTo(p)) >= 0).ToArray();
        }
    }
}
